/*
 * XQueryComponentDefinition.java
 *
 * Created on March 21, 2007, 2:46 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package com.smallx.xproc.library;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.infoset.component.Component;
import org.infoset.component.ComponentBase;
import org.infoset.component.ComponentDefinitionBase;
import org.infoset.xml.Characters;
import org.infoset.xml.Document;
import org.infoset.xml.Item;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.Text;
import org.infoset.xml.XMLException;
import org.infoset.xml.util.DocumentDestination;
import org.infoset.xml.util.DocumentSource;
import org.infoset.xml.util.NullDestination;

/**
 *
 * @author alex
 */
public class XQueryComponentDefinition extends ComponentDefinitionBase {
   
   static Name DOCUMENT_INPUT_PORT = Name.create("source");
   static Name QUERY_INPUT_PORT = Name.create("query");
   static Name RESULT_OUTPUT_PORT = Name.create("result");
   static List<Name> inputs = new ArrayList<Name>();
   static List<Name> outputs = new ArrayList<Name>();
   static {
      inputs.add(DOCUMENT_INPUT_PORT);
      inputs.add(QUERY_INPUT_PORT);
      outputs.add(RESULT_OUTPUT_PORT);
   }
   static String implClassName;
   static
   {
      implClassName = System.getProperty("com.smallx.xml.component.standard.XQueryImplementation");
      if (implClassName==null) {
         implClassName = "com.smallx.xproc.library.saxon.SaxonXQueryImplementation";
      }
   }
   static final ImplLoader implLoader = new ImplLoader();
   
   static class ImplLoader {
      
      static Implementation impl = null;
      synchronized Implementation getImplementation()
         throws XMLException
      {
         try {
            if (impl==null) {
               Class implClass = Class.forName(implClassName);
               impl = (Implementation)implClass.newInstance();
            }
            return impl;
         } catch (Exception ex) {
            throw new XMLException("Cannot load XQuery implementation class.",ex);
         }
      }
   }
   
   public interface Implementation {
      Expression compile(URI baseURI,String expr)
      throws XMLException;
   }
   
   public interface Expression {
      ItemDestination getContext()
      throws XMLException;
      void eval(URI baseURI,ItemDestination output)
      throws XMLException;
   }
   
   interface XQueryDefinition {
      
   }
   
   static class StringValueDestination implements ItemDestination {
      StringBuilder value;
      int level = -1;
      StringValueDestination() {
         value = new StringBuilder();
      }
      public void send(Item item) 
         throws XMLException
      {
         switch (item.getType()) {
            case DocumentItem:
            case ElementItem:
               level++;
               break;
            case DocumentEndItem:
            case ElementEndItem:
               level--;
         }
         if (level>=1) {
            if (item instanceof Characters) {
               value.append(((Text)item).getText());
            }
         }
      }
      public String toString() {
         return value.toString();
      }
   }
   
   public class XQueryComponent extends ComponentBase {
      
      Expression expr;
      Document inputDocument;
      URI inputResource;
      ItemDestination output;
      boolean done;
      
      public XQueryComponent(Name name,String vendor,String version) {
         super(name,vendor,version);
         this.inputDocument = null;
         this.output = null;
      }
      
      protected void bindParameters() {
      }
      
      void compile(URI base,String xquery) 
         throws XMLException
      {
         Implementation impl = implLoader.getImplementation();
         expr = impl.compile(base,xquery);
      }
      
      public void bindResource(Name port,Document document)
         throws XMLException 
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            // Save the input document
            inputDocument = document;
         } else if (port.equals(QUERY_INPUT_PORT)) {
            StringValueDestination dest = new StringValueDestination();
            DocumentSource.generate(document,false,dest);
            compile(document.getBaseURI(),dest.toString());
         } else {
            throw new XMLException("Binding port "+port+" via ItemSource is not supported.");
         }
      }
      
      public void bindResource(Name port,URI location)
         throws XMLException 
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            inputResource = location;
         } else if (port.equals(QUERY_INPUT_PORT)) {
            try {
               StringValueDestination dest = new StringValueDestination();
               context.getDocumentLoader().generate(location,dest);
               compile(location,dest.toString());
            } catch (MalformedURLException ex) {
               throw new XMLException("Cannot convert URI to URL for transform location",ex);
            } catch (IOException ex) {
               throw new XMLException("I/O error on resource: "+location, ex);
            }
         } else {
            super.bindResource(port,location);
         }
      }
      public void bindOutputPort(Name port,ItemDestination output)
         throws XMLException
      {
         if (port.equals(RESULT_OUTPUT_PORT)) {
            this.output = output;
         } else {
            throw new XMLException("Unknown output port "+port+" on "+name);
         }
      }
      
      
      public void onStart()
         throws XMLException 
      {
         done = false;
         if (expr!=null) {
            if (inputDocument!=null) {
               DocumentSource.generate(inputDocument,false,expr.getContext());
               expr.eval(inputDocument.getBaseURI(),output==null ? new NullDestination() : output);
               done = true;
            } else if (inputResource!=null) {
               try {
                  context.getDocumentLoader().generate(inputResource,expr.getContext());
                  expr.eval(inputResource,output==null ? new NullDestination() : output);
                  done = true;
               } catch (IOException ex) {
                  throw new XMLException("I/O error on resource "+inputResource,ex);
               }
            }
         }
      }
      
      public ItemDestination getInputPort(Name port)
         throws XMLException
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            if (expr==null) {
               return new DocumentDestination() {
                  public void send(Item item)
                  throws XMLException {
                     super.send(item);
                     if (item.getType()==Item.ItemType.DocumentEndItem) {
                        inputDocument = getDocument();
                        if (expr!=null) {
                           expr.eval(inputDocument.getBaseURI(),output);
                           done = true;
                        }
                     }
                  }
               };
            } else {
               return new ItemDestination() {
                  ItemDestination builder = expr.getContext();
                  URI base = null;
                  public void send(Item item)
                     throws XMLException
                  {
                     switch (item.getType()) {
                        case DocumentItem:
                           base = ((Document)item).getBaseURI();
                           builder.send(item);
                           break;
                        case DocumentEndItem:
                           builder.send(item);
                           expr.eval(base,output==null ? new NullDestination() : output);
                           break;
                        default:
                           builder.send(item);
                     }
                  }
               };
            }
         } else if (port.equals(QUERY_INPUT_PORT)) {
            return new StringValueDestination() {
               URI base;
               public void send(Item item) 
                  throws XMLException
               {
                  super.send(item);
                  switch (item.getType()) {
                     case DocumentItem:
                        base = ((Document)item).getBaseURI();
                        break;
                     case DocumentEndItem:
                        compile(base,toString());
                  }
               }
            };
         } else {
            super.getInputPort(port);
         }
         return null;
      }
      
      public void onDocumentInput(Name port,Document document)
         throws XMLException 
      {
         if (port.equals(DOCUMENT_INPUT_PORT)) {
            if (inputDocument!=null) {
               throw new XMLException("The input document has already be receieved.");
            }
            
            if (expr!=null) {
               DocumentSource.generate(document,false,expr.getContext());
               expr.eval(document.getBaseURI(),output==null ? new NullDestination() : output);
               done = true;
            }
         } else if (port.equals(QUERY_INPUT_PORT)) {
            if (expr!=null) {
               throw new XMLException("The transform document has already be receieved.");
            }
            bindResource(port,document);
            if (inputDocument!=null) {
               DocumentSource.generate(inputDocument,false,expr.getContext());
               expr.eval(inputDocument.getBaseURI(),output==null ? new NullDestination() : output);
               done = true;
            }
         } else {
            super.getInputPort(port);
         }
      }
      
      public void onEnd()
         throws XMLException 
      {
         
      }
   }
   /** Creates a new instance of IdentityComonentDefinition */
   public XQueryComponentDefinition(Name name) {
      super(name,null,null,null);
   }
   
   /** Creates a new instance of IdentityComonentDefinition */
   public XQueryComponentDefinition(Name name,String vendor,String version,URI vendorLocation) {
      super(name,vendor,version,vendorLocation);
   }
   
   public Component newInstance() 
      throws XMLException
   {
      return new XQueryComponent(getName(),getVendor(),getVersion());
   }
   
   public Collection<Name> getInputs() {
      return inputs;
   }
   
   public Collection<Name> getOutputs() {
      return outputs;
   }
}
